package im.oen.boot.common.utils;

import im.oen.boot.common.data.Base;
import im.oen.boot.common.data.Entry;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * @author Kevin.Xu
 */
public class ReflectUtil {

    static Logger logger = Logger.getLogger(ReflectUtil.class.getName());

    private static final ConcurrentHashMap<Class, ArrayList<Field>> CLASS_FIELD_CACHE = new ConcurrentHashMap<>();

    static {
        CLASS_FIELD_CACHE.clear();
    }

    public static List<Field> fetchFields(Class clazz) {
        if (clazz.equals(Object.class) || clazz.equals(Base.class)) {
            return new ArrayList<>();
        }

        ArrayList<Field> fields = CLASS_FIELD_CACHE.get(clazz);
        if (Checker.isNotEmpty(fields)) {
            return fields;
        }

        fields = new ArrayList<>();
        Field[] fieldList = clazz.getDeclaredFields();
        for (Field f : fieldList) {
            if (!Objects.equals("serialVersionUID", f.getName())) {
                fields.add(f);
            }
        }
        fields.addAll(fetchFields(clazz.getSuperclass()));
        CLASS_FIELD_CACHE.put(clazz, fields);
        return fields;
    }

    public static void setFieldValue(Object obj, Field field, Object value) {
        if (Checker.isEmpty(obj) || Checker.isEmpty(field) || Checker.isEmpty(value)) {
            return;
        }
        Runner.safeRun(() -> {
            field.setAccessible(true);
            field.set(obj, value);
        });
    }

    public static List<Entry<String, Object>> fields(Object obj) {
        if (obj == null) {
            return new ArrayList<>();
        }
        List<Field> fields = fetchFields(obj.getClass());
        return fields.stream().map(tmp -> {
            return fetch(obj, tmp);
        }).filter(tmp -> {
            return Objects.nonNull(tmp);
        }).collect(Collectors.toList());
    }

    public static List<String> fetchNullField(Object obj) {
        if (obj == null) {
            return new ArrayList<>();
        }

        List<String> nullFields = new ArrayList<>();
        List<Field> fields = fetchFields(obj.getClass());
        for (Field field : fields) {
            Entry<String, Object> data = fetch(obj, field);
            if (Checker.isEmpty(data)) {
                nullFields.add(field.getName());
            }
        }
        return nullFields;
    }

    public static <T> T newObj(Class<T> clazz) {
        if (clazz == null) {
            return null;
        }

        return Runner.safeRun(() -> {
            Constructor<T> constructor = clazz.getDeclaredConstructor();
            return constructor.newInstance();
        });
    }

    public static Entry<String, Object> fetch(Object obj, Field field) {
        if (obj == null || field == null) {
            return null;
        }

        try {
            field.setAccessible(true);
            Object value = field.get(obj);
            if (!Checker.isEmptyString(value)) {
                return new Entry(field.getName(), value);
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, e.getMessage(), e);
        }
        return null;
    }

    public static Object fetchValue(Object obj, Field field) {
        if (obj == null || field == null) {
            return null;
        }

        try {
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception e) {
            logger.log(Level.SEVERE, e.getMessage(), e);
        }
        return null;
    }
}
